package socket;


import com.sun.xml.internal.ws.handler.ClientLogicalHandlerTube;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;

/**
 * 聊天室服务端
 */
public class Server4_29 {
    /**
     * 运行在服务端的ServerSocket主要完成两个工作
     * 1：向服务端操作系统申请服务端口，客户端就是通过这个端口与ServerSocket建立连接
     * 2：监听端口，一旦一个客户端建立连接，会立即返回一个Socket。通过这个Socket就可以
     *      和该客户端交互了
     *  我们可以把ServerSocket想象成某客服的“总机”。用户打电话到总机，总机分配一个电话
     *  使得服务端与你沟通。
     */
    private ServerSocket serverSocket;
    /*
    存放所以客户端输出流，用于广播信息。
     */
    //private PrintWriter [] allOut={};
    private Collection <PrintWriter> allOut=new ArrayList<>();
    /**
     * 服务端构造方法，用来初始化
     */
    public Server4_29(){
        try {

            System.out.println("正在启动服务端.....");
            /*
            实例化ServerSocket时要指定服务端口，该端口不能与操作系统其他
            应用程序占用的端口相同，否则会抛出异常：
            java.net.BindException:address already in use

            端口是一个数字，取值范围：0-65535之间。
            6000之前的端口不要使用，密集绑定系统应用和流行应用程序。
             */
            serverSocket=new ServerSocket(8088);
            System.out.println("服务器启动完毕！");
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 服务端开始工作的方法
     */
    public void start() {
        try {
            while (true) {
                System.out.println("等待客户端连接.....");
                /*
                ServerSocket提供了接受客户端连接的方法：
                Socket accept()
                这个方法是一个阻塞方法，调用后方法“卡住”，此时开始等待客户端
                的连接，直到一个客户端连接，此时该方法会立即返回一个Socket实例
                通过这个Socket就可以与客户端进行交互了。

                可以理解为此操作是接电话，电话没响时就一直等。
                 */
                Socket socket = serverSocket.accept();
                System.out.println("一个客户端连接了！");
                //启动一个线程与客户端交互

                ClientHandler clientHandler = new ClientHandler(socket);
                Thread t=new Thread(clientHandler);
                t.start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main (String[]args){
        Server4_29 server = new Server4_29();
        server.start();
    }
        /**
         * 自定义线程任务
         * 目的是让一个线程完成与特定客户端的交互工作
         */
        private class ClientHandler implements Runnable {
            private Socket socket;
            private String host;//记录客户端的ip地址信息。
            public ClientHandler(Socket socket) {
                this.socket = socket;
                //通过socket获取远端计算机地址信息。
                host=socket.getInetAddress().getHostAddress();
            }
            @Override
            public void run() {
                PrintWriter pw=null;
                try {
                        /*
                    Socket提供的方法：
                    InputStream getInputStream();
                     获取的字节输入流读取的是对方计算机发送过来的字节。
                    */
                    InputStream is = socket.getInputStream();
                    InputStreamReader isr = new InputStreamReader(is, "utf-8");
                    BufferedReader bd = new BufferedReader(isr);

                    OutputStream out=socket.getOutputStream();
                    OutputStreamWriter osw=new OutputStreamWriter(out,"UTF-8");
                    BufferedWriter bf=new BufferedWriter(osw);
                    pw=new PrintWriter(bf,true);

                    //将输出流存入共享数组allOut中
//                    synchronized(this){//不行，因为这个是ClientHander实例
//                    synchronized(allOut){//不行，下面操作会扩容，allOut对象会变。
//                    synchronized (Server4_29.this) {//外部类对象可以
                        //1：对allOut数组扩容
                        //allOut = Arrays.copyOf(allOut, allOut.length + 1);
                        //2：将输出流存入数组最后一个位置。
                        //allOut[allOut.length - 1] = pw;
                    /*
                    换成集合后，锁对象可以直接使用该临界资源allOut了，因为
                    增删元素并不会创建一个新的集合赋值给allOut（即：allOut
                    对象本身没有发生过改变，只是内部元素增删而已。
                     */
                    synchronized(allOut){
                        allOut.add(pw);
                    }
                        //通知所以客户端用户上线了
                        //sendMessage(host + "上线了，当前在线人数为：" + allOut.length);
                    sendMessage(host + "上线了，当前在线人数为：" + allOut.size());


                    String line;
                    while ((line = bd.readLine()) != null) {
                        System.out.println(host+"说：" + line);
                        //将消息回复给所以客户端
                        sendMessage(host+"说"+line);

                    }
                } catch (IOException e) {
                    System.out.println("对方退出系统");
                    e.printStackTrace();
                }finally{
                    //处理客户端断开链接的操作
                    //将当前客户端的输出流从allOut中删除（数组缩容).
//                    synchronized (Server4_29.this) {
//                        for(int i=0;i<allOut.length;i++){
//                            if(allOut[i]==pw){
//                                allOut[i]=allOut[allOut.length-1];
//                                allOut=Arrays.copyOf(allOut,allOut.length-1);
//                            }
//                        }
//                        Iterator <PrintWriter> it=allOut.iterator();
//                        while(it.hasNext()){
//                            if(it.next()==pw){
//                                it.remove();
//                                break;
//                            }
//                        }
                    synchronized(allOut){
                        allOut.remove(pw);
                    }
                    //sendMessage(host+"下线了，当前在线人数为："+allOut.length);
                    sendMessage(host+"下线了，当前在线人数为："+allOut.size());
                    try {
                        socket.close();//与客户端断开链接。
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

            }

            /**
             * 广播消息给所有客户端
             * @param message
             */
            private void sendMessage(String message){
//                synchronized (Server4_29.this) {
//                    for(int i=0;i<allOut.length;i++){
//                        allOut[i].println(message);
//                    }
                synchronized(allOut){
                    for(PrintWriter pw:allOut){
                        pw.println(message);
                    }
                }
            }
        }

    }

